package com.example.walmartapidemo.common.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.*;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.IOUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.api.R;
import com.example.walmartapidemo.common.constant.WalmartConstant;
import com.example.walmartapidemo.domain.DcWalmartSellers;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
public class WalmartRequest {

    /**
     * 发送get请求
     *
     * @param requestUrl 请求url,可带参数
     * @param id         店铺ID
     * @author senghor
     * @date 2022/5/12 16:48
     */
    public static String request(String requestUrl, Method method, Long id) throws Exception {
        return request(requestUrl, method, id, null);
    }

    /**
     * 发送get请求
     *
     * @param requestUrl 请求url,可带参数
     * @param id         店铺ID
     * @param paramMap   请求参数，根据请求方式区分，get会追加到url后,post则为json
     * @author senghor
     * @date 2022/5/12 16:48
     */
    public static String request(String requestUrl, Method method, Long id, Map<String, ?> paramMap) throws Exception {
        DcWalmartSellers walmartSellers = getShopSettings(id);
        HttpResponse response = null;
        try {
            if (Method.GET == method) {
                // get请求拼接参数
                if (paramMap != null && paramMap.size() > 0) {
                    if (requestUrl.indexOf("?") > 0) {
                        requestUrl = requestUrl + "&" + HttpUtil.toParams(paramMap);
                    } else {
                        requestUrl = requestUrl + "?" + HttpUtil.toParams(paramMap);
                    }
                }
            }
            Map<String, String> headers = getHeaders(walmartSellers, requestUrl, method, paramMap);
            log.info("walmart本次请求路径:" + requestUrl);
            HttpRequest httpRequest = HttpRequest
                    .of(requestUrl)
                    .setMethod(method)
                    .addHeaders(headers)
                    .contentType(ContentType.JSON.getValue());
            response = httpRequest.execute();
            log.info("response body" + response.body());
            if (HttpStatus.HTTP_OK == response.getStatus()) {
                return isCsv(response) ? resolveCsv(response) : response.body();
            }else {
                if (id ==60) throw new Exception("调用Walmart API 错误");
            }
        } catch ( Exception e) {
            log.error(requestUrl + "获取Walmart token 错误:" + e.getMessage(), e);
            throw new Exception("获取Walmart token 异常" + e);
        } catch (Exception e) {
            log.error(requestUrl + "调用Walmart API 错误" + e.getMessage(), e);
            throw new Exception("调用Walmart API 异常:" + e);
        } finally {
            if (ObjectUtils.isNotEmpty(response)){
                response.close();
            }
        }
        return null;
    }

    /**
     * 获取头部信息 - Content-Disposition
     *
     * @param response
     * @return
     */
    private static String getContentDisposition(HttpResponse response) {
        return response.header("Content-Disposition");
    }

    /**
     * 判断是否是csv文件
     *
     * @param response
     * @return
     */
    private static boolean isCsv(HttpResponse response) {
        String header = response.header("Content-Disposition");
        return StrUtil.isNotBlank(header) && header.contains("filename=");
    }

    /**
     * 解析cvs
     *
     * @param response
     */
    public static String resolveCsv(HttpResponse response) {
        InputStream inputStream = response.bodyStream();
        try {
            String header = getContentDisposition(response);
            //header value will be something like:
            //attachment; filename=10000000354_2016-01-15T23:09:54.438+0000.zip
            int length = header.length();
            String fileName = header.substring(header.indexOf("filename="), length);
//            System.out.println("filenameText " + fileName);
            String[] str = fileName.split("=");
            //replace "/Users/anauti1/Documents/" below with your values
//            File reportFile = new File(resource.getPath() + "\\" + str[1].toString());
            File reportFile = getTemporaryFile(str[1]);
            OutputStream outStream = new FileOutputStream(getTemporaryFile(str[1]));
            byte[] buffer = new byte[8 * 1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, bytesRead);
            }
            IOUtils.closeQuietly(inputStream);
            IOUtils.closeQuietly(outStream);
            return reportFile.getPath();
        } catch (Exception ex) {
            log.error("Exception: " + ex.getMessage(), ex);
        }
        return "";
    }

    private static File getTemporaryFile(String filename) {
        return new File("D:\\home\\ec2-user\\walmart\\payment\\" + filename);
    }

    public static DcWalmartSellers getShopSettings(Long accountId) {
        RemoteWalmartSellersService remoteWalmartSellersService = SpringUtils.getBean(RemoteWalmartSellersService.class);
        RedisService redisService = SpringUtils.getBean(RedisService.class);
        String shopKey = WalmartConstant.SHOP_SETTINGS + accountId;
        log.info("redis walmart shop info ,{}", JSON.toJSONString(redisService.getCacheObject(shopKey)));
        DcWalmartSellers shopSettings = redisService.getCacheObject(shopKey);
        if (shopSettings != null) {
            return shopSettings;
        }
        R<DcWalmartSellers> r = remoteWalmartSellersService.selectById(accountId);
        if (r.getCode() != 200) {
            throw new Exception("获取授权信息异常" + r.getMsg());
        }
        //redis中没有，则查询数据库
        shopSettings = r.getData();
        redisService.setCacheObject(shopKey, shopSettings, 24L, TimeUnit.HOURS);
        return shopSettings;
    }

    /**
     * 获取美国店铺的token
     *
     * @return
     */
    public static String getAccessToken(String authorization, DcWalmartSellers sellers) {
        RedisService redisService = SpringUtils.getBean(RedisService.class);
        if (redisService.hasKey(sellers.getAccountName())) {
            return redisService.getCacheObject(sellers.getAccountName());
        }
        String accessToken = "";
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", authorization);
        headers.put("WM_SVC.NAME", sellers.getAccountName());
        headers.put("WM_QOS.CORRELATION_ID", IdUtil.randomUUID());
        try {
            HttpResponse response = HttpRequest.post(WalmartConstant.BASE_URL + WalmartConstant.USA_TOKEN_URL).addHeaders(headers).body("grant_type=client_credentials", "application/x-www-form-urlencoded").execute();
            JSONObject jsonObject = JSONUtil.parseObj(response.body());
            accessToken = jsonObject.getStr("access_token");
            if (accessToken == null) {
                log.error("walmart获取token请求失败body:{}",response.body());
                throw new Exception("获取沃尔玛接口调用凭证失败", 500);
            }
        } catch (Exception e) {
            log.error("【获取美国站点token值异常】，{}", e.getMessage());
            throw new Exception("【获取美国站点token值异常】，{}", 500);
        }
        // 缓存token信息一小时
        redisService.setCacheObject(sellers.getAccountName(), accessToken, 800, TimeUnit.SECONDS);
        return accessToken;
    }


    /**
     * 获取访问加拿大店铺API的数字签名和时间戳参数
     *
     * @param seller     店铺数据
     * @param requestUrl 请求url
     * @param method     请求方式
     * @param paramMap   请求参数
     * @author senghor
     * @date 2022/5/13 14:19
     */
    public static void setSignatureAndTimestamp(DcWalmartSellers seller, String requestUrl
            , Method method, Map<String, ?> paramMap, Map<String, String> headers) {
        if (method == null || StrUtil.isEmpty(requestUrl)) {
            throw new Exception(BaseError.CHECK_ERROR);
        }
        String timestamp = String.valueOf(System.currentTimeMillis());
        String signature = getWalmartAuthSignature(seller, requestUrl, method, paramMap, timestamp);
        headers.put("WM_SEC.TIMESTAMP", timestamp);
        headers.put("WM_SEC.AUTH_SIGNATURE", signature);
    }

    /**
     * 获取沃尔玛认证数字签名
     *
     * @param seller     店铺数据
     * @param requestUrl 请求url
     * @param method     请求方式
     * @param paramMap   请求参数
     * @param timestamp  时间戳
     * @return
     */
    public static String getWalmartAuthSignature(DcWalmartSellers seller, String requestUrl, Method method, Map<String, ?> paramMap, String timestamp) {
        //登录后从开发者中心获取的Consumer ID
        String consumerId = seller.getConsumerId();
        //私钥
        String privateKey = seller.getPrivateKey();
        String stringToSign = consumerId + "\n" + requestUrl + "\n" + method.name() + "\n" + timestamp + "\n";
        return signData(stringToSign, privateKey);
    }

    /**
     * 签名算法
     *
     * @param stringToBeSigned  签名结构  消费者ID（Consumer ID） + "\n" + 完整路径(url) + "\n" + 时间戳(timestamp) + "\n";
     * @param encodedPrivateKey 私密秘钥Private Key
     * @return
     */
    public static String signData(String stringToBeSigned, String encodedPrivateKey) {
        String signatureString = null;
        try {
            byte[] encodedKeyBytes = Base64.decodeBase64(encodedPrivateKey);
            PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encodedKeyBytes);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            PrivateKey myPrivateKey = kf.generatePrivate(privSpec);
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(myPrivateKey);
            byte[] data = stringToBeSigned.getBytes(StandardCharsets.UTF_8);
            signature.update(data);
            byte[] signedBytes = signature.sign();
            signatureString = Base64.encodeBase64String(signedBytes);
        } catch (Exception e) {
            log.info("签名算法异常", e);
        }
        return signatureString;
    }

    /**
     * 对密钥进行编码
     *
     * @param appKey    app key
     * @param appSecret app秘钥
     * @return bese64编码字符串
     */
    public static String getAuthorization(String appKey, String appSecret) {
        String str = appKey + ":" + appSecret;
        return "Basic " + Base64.encodeBase64String(str.getBytes());
    }

    /**
     * 获取访问Us站点API的请求头
     *
     * @param seller     店铺数据
     * @param requestUrl 请求url
     * @param method     请求方式
     * @param paramMap   请求参数
     * @return 返回请求头map
     */
    public static Map<String, String> getHeaders(DcWalmartSellers seller, String requestUrl
            , Method method, Map<String, ?> paramMap) {
        String authorization = getAuthorization(seller.getAppKey(), seller.getAppSecret());
        Map<String, String> headers = new HashMap<>();
        headers.put("WM_SVC.NAME", seller.getAccountName());
        headers.put("WM_QOS.CORRELATION_ID", IdUtil.randomUUID());
        if (seller.getSite().equals("US")) {
            headers.put("Authorization", authorization);
            headers.put("WM_SEC.ACCESS_TOKEN", getAccessToken(authorization, seller));
        } else if (seller.getSite().equals("CA")) {
            setSignatureAndTimestamp(seller, requestUrl, method, paramMap, headers);
            headers.put("WM_CONSUMER.CHANNEL.TYPE", seller.getChannelType());
            headers.put("WM_CONSUMER.ID", seller.getConsumerId());
            headers.put("WM_TENANT_ID", "WALMART.CA");
            headers.put("WM_LOCALE_ID", "en_CA");
        }
        return headers;
    }

    public static void main(String[] args) {
        File file =  new File("/home/admin-root/erp/file/walmart/payment/test");
        FileUtil.writeString("1234" ,file, "utf-8");
    }
}
